package lex.fractal.mandelbrotset;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.Stack;

import javax.swing.JLabel;
import javax.swing.JPanel;

public class MSPane extends JPanel
{
	private JLabel statusLabel = null;
	private double imMax = 1.5; // Max. range of imag./vert.-pixel axis.
	private double imMin = -1.5; // Min. range of imag./vert.-pixel axis.
	private double reMax = 1.5; // Max. range of real/horiz.-pixel axis.
	private double reMin = -2.5; // Min. range of real/horiz.-pixel axis.
	private int level = 0;
	private Stack<Complex> stack = new Stack<Complex>();

	private static class Complex
	{
		double re;
		double im;
	}

	public MSPane( JLabel statusLabel )
	{
		this.statusLabel = statusLabel;
		addMouseListener( new MouseAdapter(){
			public void mouseClicked( MouseEvent e )
			{
				int x = e.getX();
				int y = e.getY();
				if( !e.isShiftDown() && level < 31 )
				{
					double re = reMin + x * (reMax - reMin) / getWidth();
					double im = imMin + y * (imMax - imMin) / getHeight();
					Complex cn = new Complex();
					cn.re = re;
					cn.im = im;
					stack.push( cn );
					reMax += re;
					reMin += re;
					imMax += im;
					imMin += im;
					reMax /= 2.0;
					reMin /= 2.0;
					imMax /= 2.0;
					imMin /= 2.0;
					level++;
				}
				else if( e.isShiftDown() && level != 0 )
				{
					Complex cn = stack.pop();
					double re = cn.re;
					double im = cn.im;
					reMax *= 2.0;
					reMin *= 2.0;
					imMax *= 2.0;
					imMin *= 2.0;
					reMax -= re;
					reMin -= re;
					imMax -= im;
					imMin -= im;
					level--;
				}
				updateStatusLabel( x, y );
				repaint();
			}
		} );
		addMouseMotionListener( new MouseMotionAdapter(){
			public void mouseMoved( MouseEvent e )
			{
				updateStatusLabel( e.getX(), e.getY() );
			}
		} );
	}

	private StringBuilder buf = new StringBuilder();

	private void updateStatusLabel( int x, int y )
	{
		buf.setLength( 0 );
		buf.append( "Level: " ).append( level );
		buf.append( ", x: " ).append( x );
		buf.append( ", y: " ).append( y );
		statusLabel.setText( buf.toString() );
	}

	public void paintComponent( Graphics g )
	{
		// The following increments make it possible to accurately map
		// floating-point numbers in the complex plane to integers on this
		// component's display surface.
		double imInc = (imMax - imMin) / getHeight();
		double reInc = (reMax - reMin) / getWidth();
		double dist = 0.0, im, re, tim, tre, zim, zre;
		int iter, x, y;
		// Generate Mandelbrot set. Each (re, im) combination corresponds
		// to point c on the complex plane.
		for( im = imMin, y = 0; im <= imMax; im += imInc, y++ )
			for( re = reMin, x = 0; re <= reMax; re += reInc, x++ )
			{
				// The (zre, zim) combination corresponds to complex
				// number z.
				zim = 0.0;
				zre = 0.0;
				for( iter = 1; iter < Palette.palette.length; iter++ )
				{
					// Calculate z*z.
					tim = (zre + zre) * zim;
					tre = zre * zre - zim * zim;
					// Add c to z*z.
					zim = tim + im;
					zre = tre + re;
					// Calculate distance squared.
					dist = zre * zre + zim * zim;
					// If distance squared exceeds 4.0, point is outside
					// Mandelbrot set, so color point to a non-black color.
					if( dist > 4.0 )
					{
						g.setColor( Palette.palette[iter] );
						g.drawLine( x, y, x, y );
						break;
					}
				}
				// If distance squared is less than or equal to 4.0, point
				// is inside Mandelbrot set, so color point black.
				if( dist <= 4.0 )
				{
					g.setColor( Color.black );
					g.drawLine( x, y, x, y );
				}
			}
	}
}
